use std::{collections::HashMap, fs::Metadata, path::{PathBuf, MAIN_SEPARATOR_STR}, sync::Arc, time::{Duration, SystemTime}};

use dav_server::{davpath::DavPath, fs::{DavDirEntry, DavFile, DavMetaData, FsError, FsFuture, FsResult, FsStream, GuardedFileSystem, OpenOptions as FsOpenOptions, ReadDirMeta}};
use tokio_util::bytes::BytesMut;
use wtask_base::{
    app::get_wtask_data_dir,
    cmd::create_file,
    error::{WError, WResult},
    export::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, File, OpenOptions, TokioRwLock, create_dir_all, log_debug, log_error, log_info},
    machine::get_machine_name
};
use wtask_app::{AppFileDownload, AppFileTree, AppFileUpload, FileUDArgs, FutureExt, cli_app, msg_func_none, stream_iter};

use super::base::MachineDavCre;


#[derive(Debug, Clone)]
struct MachineMetaData {
    len: u64,
    modified: SystemTime,
    dir: bool
}

impl From<Metadata> for MachineMetaData {
    fn from(value: Metadata) -> Self {
        Self {
            len: value.len(),
            modified: value.modified().unwrap(),
            dir: value.is_dir()
        }
    }
}

impl DavMetaData for MachineMetaData {
    fn len(&self) -> u64 {
        self.len
    }
    
    fn modified(&self) -> FsResult<SystemTime> {
        Ok(self.modified)
    }
    
    fn is_dir(&self) -> bool {
        self.dir
    }
}


#[derive(Debug)]
struct MachineEntry {
    name: String,
    meta: MachineMetaData,
}
impl DavDirEntry for MachineEntry {
    fn name(&self) -> Vec<u8> {
        self.name.as_bytes().to_owned()
    }

    fn metadata(&self) -> FsFuture<Box<dyn DavMetaData>> {
        async move {
            Ok(Box::new(self.meta.clone()) as Box<dyn DavMetaData>)
        }.boxed()
    }
}


#[derive(Debug)]
struct MachineFile {
    remote: PathBuf,
    last_save: SystemTime,
    config: MachineDavCre,
    file: File
}

impl MachineFile {
    async fn file(p_local_raw: PathBuf, config: &MachineDavCre) -> WResult<(PathBuf, PathBuf)> {
        // 从home开始
        let p_local_raw = p_local_raw.to_string_lossy();
        let p_local_raw = p_local_raw.trim_start_matches(['/', '\\']);
        let p_remote = PathBuf::from(&config.1).join(p_local_raw);
        let p_local = get_wtask_data_dir("webdav", false)?
            .join(get_machine_name(&config.0.machine_token))
            .join(p_local_raw);
        log_info!("file: {:?} {:?}", p_remote, p_local);
        if !p_local.exists() {
            let dir = p_local.parent().unwrap();
            if !dir.exists() {
                create_dir_all(&dir).await?;
            }
            if p_local_raw.to_lowercase().ends_with("desktop.ini") || p_local_raw.to_lowercase().ends_with("autorun.inf") {
                create_file(&p_local, "")?;
            } else {
                log_debug!("machine webdav file: {p_remote:?} {p_local:?} downloading ...");
                let mut args = FileUDArgs::new(p_remote.clone());
                args.overwrite = true;
                let mut app = AppFileDownload::from(args);
                app.dir = Some(p_local.parent().unwrap().to_path_buf());
                log_info!("Req file load: {:?} ...", p_remote);
                let _ = cli_app(app, &config.0, None, msg_func_none).await;
            }
        };
        if !p_local.exists() {
            return Err(WError::DataError(format!("No file: {:?} {:?}", p_remote, p_local)));
        }
        Ok((p_remote, p_local))
    }

    async fn new(remote: PathBuf, options: FsOpenOptions, config: MachineDavCre) -> WResult<Self> {
        let (remote, p) = Self::file(remote, &config).await?;
        
        let file = OpenOptions::new()
            .read(options.read)
            .write(options.write)
            .append(options.append)
            .truncate(options.truncate)
            .create(options.create)
            .create_new(options.create_new)
            .open(p)
            .await?;
        Ok(Self {
            remote,
            last_save: SystemTime::now(),
            config,
            file
        })
    }
}


impl DavFile for MachineFile {
    fn metadata(&mut self) -> FsFuture<Box<dyn DavMetaData>> {
        async move {
            let meta = self.file.metadata().await?;
            Ok(Box::new(MachineMetaData::from(meta)) as Box<dyn DavMetaData>)
        }
        .boxed()
    }

    fn flush(&mut self) -> FsFuture<()> {
        async move {
            self.file.flush().await?;
            let save_dur = Duration::from_secs(6);
            if self.last_save.elapsed().unwrap() > save_dur {
                let app = AppFileUpload::from(FileUDArgs::new(self.remote.clone()));
                log_info!("Req file flush: {:?} ...", self.remote);
                let v = cli_app(
                    app,
                    &self.config.0,
                    None,
                    msg_func_none
                ).await;
                if v.is_err() {
                    return Err(FsError::GeneralFailure)
                }
            }
            self.last_save = SystemTime::now();
            Ok(())
        }
        .boxed()
    }

    fn read_bytes(&mut self, count: usize) -> FsFuture<tokio_util::bytes::Bytes> {
        async move {
            let mut buf = BytesMut::with_capacity(count);
            self.file.read_buf(&mut buf).await?;
            let res = buf.freeze();
            Ok(res)
        }.boxed()
    }

    fn seek(&mut self, pos: std::io::SeekFrom) -> FsFuture<u64> {
        async move {
            let res = self.file.seek(pos).await?;
            Ok(res)
        }.boxed()
    }

    fn write_buf(&mut self, mut buf: Box<dyn tokio_util::bytes::Buf + Send>) -> FsFuture<()> {
        async move {
            self.file.write_buf(&mut buf).await?;
            Ok(())
        }.boxed()
    }

    fn write_bytes(&mut self, mut buf: tokio_util::bytes::Bytes) -> FsFuture<()> {
        async move {
            self.file.write_buf(&mut buf).await?;
            Ok(())
        }.boxed()
    }
}





#[derive(Debug, Clone)]
pub(super) struct MachineFs {
    meta: Arc<TokioRwLock<HashMap<String, MachineMetaData>>>
}

impl MachineFs {
    pub(super) fn new() -> Box<Self> {
        let mut meta = HashMap::new();
        meta.insert(MAIN_SEPARATOR_STR.to_string(), MachineMetaData {
            len: 0,
            modified: SystemTime::now(),
            dir: true
        });
        Box::new(Self { meta: Arc::new(TokioRwLock::new(meta)) })
    }


    async fn read_dir_with_refresh(&self, path: &DavPath, credentials: &MachineDavCre) -> WResult<Vec<FsResult<Box<dyn DavDirEntry>>>> {
        let p_local_raw = path.to_string();
        let p_local_raw = p_local_raw.trim_start_matches(['/', '\\']);
        let p_remote = PathBuf::from(&credentials.1).join(p_local_raw);
        if p_local_raw.to_lowercase().ends_with("desktop.ini") || p_local_raw.to_lowercase().ends_with("autorun.inf") {
            let m = MachineMetaData {
                len: 0,
                modified: SystemTime::now(),
                dir: false
            };
            self.meta.write().await.insert(path.to_string(), m.clone());
            return Ok(vec![]);
        }


        log_info!("Req dir: {:?} ...", p_remote);
        let app = AppFileTree {
            dir: p_remote,
            hidden: Some(".".to_owned()),
            deep: 1,
        };
        let f = cli_app(
            app,
            &credentials.0,
            None,
            msg_func_none
        ).await;
        if f.is_err() {
            return Err(WError::DataError(format!("path req error: {}", f.err().unwrap())))
        }
        let mut res = Vec::new();
        if let Ok(Some(tree)) = f {
            log_debug!("file-tree: {tree}");
            
            let r_str = path.to_string();
            let r = PathBuf::from(&r_str);
            
            let m = MachineMetaData {
                len: tree.tree.len,
                modified: tree.tree.motified,
                dir: tree.tree.is_dir
            };
            self.meta.write().await.insert(r_str, m);

            for v in tree.tree.children {
                let f = r.join(&v.path).to_string_lossy().to_string();
                let m = MachineMetaData {
                    len: v.len,
                    modified: v.motified,
                    dir: v.is_dir
                };
                res.push(Ok(Box::new(MachineEntry {
                    name: v.path,
                    meta: m.clone()
                }) as Box<dyn DavDirEntry>));
                self.meta.write().await.insert(f, m);
            }
        }
        Ok(res)
    }


    async fn read_metadata(&self, path: &DavPath, credentials: &MachineDavCre) -> WResult<MachineMetaData> {
        let p = path.to_string();
        let m = self.meta.read().await.get(&p).cloned();
        if let Some(m) = m {
            return Ok(m)
        }

        let _ = self.read_dir_with_refresh(path, credentials).await;

        self.meta.read().await.get(&p).cloned().ok_or(WError::DataError(format!("No meta: {p}")))
    }
}

impl GuardedFileSystem<MachineDavCre> for MachineFs {
    fn copy<'a>(
            &'a self,
            from: &'a DavPath,
            to: &'a DavPath,
            _credentials: &'a MachineDavCre,
        ) -> FsFuture<'a, ()> {
        async move {
            log_error!("copy {:?} {:?}", from, to);
            Err(FsError::Forbidden)
        }.boxed()
    }

    fn create_dir<'a>(
            &'a self,
            path: &'a DavPath,
            _credentials: &'a MachineDavCre
        ) -> FsFuture<'a, ()> {
        async move {
            log_error!("create_dir {:?}", path);
            Err(FsError::Forbidden)
        }.boxed()
    }

    fn metadata<'a>(
            &'a self,
            path: &'a DavPath,
            credentials: &'a MachineDavCre,
        ) -> FsFuture<'a, Box<dyn DavMetaData>> {
        async move {
            log_debug!("machine webdav metadata {path}");
            let meta = self.read_metadata(path, credentials).await;
            match meta {
                Ok(meta) => {
                    Ok(Box::new(meta) as Box<dyn DavMetaData>)
                },
                Err(e) => {
                    log_error!("machine webdav metadata error: {path} {e}");
                    Err(FsError::GeneralFailure)
                }
            }
        }.boxed()
    }

    fn open<'a>(
            &'a self,
            path: &'a DavPath,
            options: FsOpenOptions,
            credentials: &'a MachineDavCre,
        ) -> FsFuture<'a, Box<dyn DavFile>> {
        async move {
            log_info!("machine webdav open {path}");
            let file = MachineFile::new(
                path.as_pathbuf(),
                options,
                credentials.clone()
            ).await;
            match file {
                Ok(f) => Ok(Box::new(f) as Box<dyn DavFile>),
                Err(e) => {
                    log_error!("machine webdav open error: {path} {e}");
                    Err(FsError::GeneralFailure)
                }
            }
        }.boxed()
    }

    fn read_dir<'a>(
            &'a self,
            path: &'a DavPath,
            _meta: ReadDirMeta,
            credentials: &'a MachineDavCre,
        ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
        async move {
            log_debug!("machine webdav read dir {path}");
            let v = self.read_dir_with_refresh(path, credentials).await;
            match v {
                Ok(v) => Ok(Box::pin(stream_iter(v)) as FsStream<Box<dyn DavDirEntry>>),
                Err(e) => {
                    log_error!("machine webdav read dir error: {path} {e}");
                    Err(FsError::GeneralFailure)
                }
            }
        }.boxed()
    }

    fn remove_dir<'a>(&'a self, path: &'a DavPath, _credentials: &'a MachineDavCre) -> FsFuture<'a, ()> {
        async move {
            log_error!("remove_dir {:?}", path);
            Err(FsError::Forbidden)
        }.boxed()
    }

    fn remove_file<'a>(&'a self, path: &'a DavPath, _credentials: &'a MachineDavCre) -> FsFuture<'a, ()> {
        async move {
            log_error!("remove_file {:?}", path);
            Err(FsError::Forbidden)
        }.boxed()
    }

    fn rename<'a>(
            &'a self,
            from: &'a DavPath,
            to: &'a DavPath,
            _credentials: &'a MachineDavCre,
        ) -> FsFuture<'a, ()> {
        async move {
            log_error!("rename {:?} {:?}", from, to);
            Err(FsError::Forbidden)
        }.boxed()
    }
}